/*
 * FileName: ls3.c
 * Author: mofei
 * History: 2014-07-17, modified 2014-07-19
 * Descibe: purpose list contents of directory or directories
 *          action if no args, use . else list files in args
 *          note uses stat and pwd.h and grp.h
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>

void do_ls(char []);
void dostat(char *, int, int);
void show_file_info(char *, struct stat *);
void mode_to_letters(int, char []);
char *uid_to_name(uid_t);
char *gid_to_name(gid_t);

int main(int argc, char *argv[]) {
    if(argc == 1)
        do_ls(".");
    else
        while(--argc) {
            printf("%s:\n", *++argv);
            do_ls(*argv);
        }
}

/* list files in directory called dirname */
void do_ls(char dirname[]) {
    DIR *dir_ptr;               /* the directory */
    struct dirent *direntp;     /* each entry */
    char str[256];
    int file_len, dir_len;

    if((dir_ptr = opendir(dirname)) == NULL) {
        file_len = strlen(dirname);
        dostat(dirname, 0, file_len);
    } else {
        while((direntp = readdir(dir_ptr)) != NULL) {
            strcpy(str, dirname);
            strcat(str,"/");
            dir_len = strlen(dirname) + 1;
            file_len = strlen(direntp->d_name);
            strcat(str,direntp->d_name);
            dostat(str, dir_len, file_len);
        }
        closedir(dir_ptr);
    }
}

void dostat(char *filename, int dir_len, int file_len) {
    struct stat info;

    if(stat(filename, &info) == -1)        /* cannot stat */
        perror(filename);                  /* say why */
    else {
        strncpy(filename, filename+(dir_len), file_len);
        filename[file_len] = '\0';
        show_file_info(filename, &info);
    }
}

/* display the info about filename. The info is stored in struct at *info_p */
void show_file_info(char *filename, struct stat *info_p) {
    char *uid_to_name(), *ctime(), *gid_to_name(), *filemode();
    void mode_to_letters();
    char modestr[11];

    mode_to_letters(info_p->st_mode, modestr);

    printf("%s", modestr);
    printf("%4d ", (int)info_p->st_nlink);
    printf("% -8s", uid_to_name(info_p->st_uid));
    printf("% -8s", gid_to_name(info_p->st_gid));
    printf("%8ld ", (long)info_p->st_size);
    printf("%.12s ", 4+ctime(&info_p->st_mtime));
    printf("%s\n", filename);
}

/*
 * utility functions
 * This function takes a mode value and a char array
 * and puts into the char array the file type and the
 * nine letters that correspond to the bits in mode.
 * Note: It does not code setuid, setgid, and sticky
 * codes.
 */
void mode_to_letters(int mode, char str[]) {
    strcpy(str, "----------");            /* default = no perms */

    if(S_ISDIR(mode)) str[0] = 'd';       /* directory */
    if(S_ISCHR(mode)) str[0] = 'c';       /* char devices */
    if(S_ISBLK(mode)) str[0] = 'b';       /* block devices */
    
    if(mode & S_IRUSR) str[1] = 'r';      /* 3 bit for user */
    if(mode & S_IWUSR) str[2] = 'w';     
    if(mode & S_IXUSR) str[3] = 'x';
    
    if(mode & S_IRGRP) str[4] = 'r';      /* 3 bit for group */
    if(mode & S_IWGRP) str[5] = 'w';
    if(mode & S_IXGRP) str[6] = 'x';

    if(mode & S_IROTH) str[7] = 'r';      /* 3 bit for other */
    if(mode & S_IWOTH) str[8] = 'w';
    if(mode & S_IXOTH) str[9] = 'x';

//    if(mode & S_ISUID) str[3] = 's';      /* 3 bit for special file */
//    if(mode & S_ISGID) str[6] = 's';
//    if(mode & S_ISVTX) str[9] = 't';
}

#include <pwd.h>

/* returns pointer to username associated with uid, uses getpw() */
char *uid_to_name(uid_t uid) {
    struct passwd *getpwuid(), *pw_ptr;
    static char numstr[10];

    if((pw_ptr = getpwuid(uid)) == NULL) {
        sprintf(numstr, "%d", uid);
        return numstr;
    } else
        return pw_ptr->pw_name;
}

#include <grp.h>

/* returns pointer to group number gid, used getgrgid(3) */
char *gid_to_name(gid_t gid) {
    struct group *getgrgid(), *grp_ptr;
    static char numstr[10];

    if((grp_ptr = getgrgid(gid)) == NULL) {
        sprintf(numstr, "%d", gid);
        return numstr;
    } else 
        return grp_ptr->gr_name;
}

